#include "arithcodertest.h"
#include <QFileDialog>
#include "encodedialog.h"
#include "ArithmeticEncoder.h"
#include "ArithmeticDecoder.h"
#include "MockProbabilityModel.h"
#include "ContextTree.h"
#include <cstdio>
#include <ctime>
#include <QErrorMessage>

ArithCoderTest::ArithCoderTest(QWidget *parent, Qt::WFlags flags)
	: QMainWindow(parent, flags), dataSource(this)
{
	ui.setupUi(this);
	ui.tableView->setModel(&dataSource);
}

ArithCoderTest::~ArithCoderTest()
{
	if (openedFile.isOpen())
	{
		openedFile.close();
	}
}

void ArithCoderTest::openFile()
{
	if (openedFile.isOpen())
	{
		openedFile.close();
	}
	QFileDialog dialog;
	dialog.setFileMode(QFileDialog::ExistingFile);
	dialog.setAcceptMode(QFileDialog::AcceptOpen);
	int result = dialog.exec();
	if (result == QFileDialog::Accepted)
	{
		openedFilename = dialog.selectedFiles()[0];
		openedFile.setFileName(openedFilename);

		if (openedFilename.endsWith(".comp"))
		{
			ui.actionEncode_file->setEnabled(false);
			ui.actionDecode_file->setEnabled(true);
		}
		else
		{
			ui.actionEncode_file->setEnabled(true);
			ui.actionDecode_file->setEnabled(false);
		}
	}
}

void ArithCoderTest::encodeFile()
{
	EncodeDialog dialog;
	int result = dialog.exec();
	if (result == QDialog::Accepted)
	{
		FILE* file = fopen(openedFilename.toAscii().constData(), "rb");
		int size = fileSize(file);
		char* aFile = new char[size];
		fread(aFile, 1, size, file);
		fclose(file);

		if (dialog.usesCTW())
		{
			clock_t start = clock();
			ContextTree tree(8, 1);
			ArithmeticEncoder encoder(&tree, 1, 8);
			unsigned long long symbolsEncoded = 0;
			unsigned int bits = 0;
			BitBuffer buffer = encoder.encodeString(aFile, &symbolsEncoded, size);
			bits = buffer.numberOfBits();

			//update table
			clock_t end = clock();
			delete[] aFile;
			char text[20];
			sprintf(text, "%d", end - start);
			char bitRateText[20];
			sprintf(bitRateText, "%f", bits / ((float)size));
			dataSource.addEntry(openedFilename, QString("encode withoutCTW"), QString(text), QString(bitRateText));

			//write compressed file
			openedFilename.append(".comp");
			FILE* f = fopen(openedFilename.toAscii().constData(), "wb");
			fwrite(&symbolsEncoded, sizeof(symbolsEncoded), 1, f);
			fwrite(&bits, sizeof(bits), 1, f);
			int bytes = bits / 8;
			if (bits % 8 != 0)
			{
				++bytes;
			}
			fwrite(buffer.getBuffer(), 1, bytes, f);
			fflush(f);
			fclose(f);

			openedFilename.remove(".comp");
		}
		else
		{

			clock_t start = clock();
			MockProbabilityModel model(aFile, size, dialog.symbolLength());
			ArithmeticEncoder encoder(&model);
			unsigned long long symbolsEncoded = 0;
			unsigned int bits = 0;
			BitBuffer buffer = encoder.encodeString(aFile, &symbolsEncoded, size);
			bits = buffer.numberOfBits();

			//update table
			clock_t end = clock();
			delete[] aFile;
			char text[20];
			sprintf(text, "%d", end - start);
			char bitRateText[20];
			sprintf(bitRateText, "%f", bits / ((float)size));
			dataSource.addEntry(openedFilename, QString("encode withoutCTW"), QString(text), QString(bitRateText));

			//write compressed file
			openedFilename.append(".comp");
			FILE* f = fopen(openedFilename.toAscii().constData(), "wb");
			fwrite(&symbolsEncoded, sizeof(symbolsEncoded), 1, f);
			fwrite(&bits, sizeof(bits), 1, f);
			int bytes = bits / 8;
			if (bits % 8 != 0)
			{
				++bytes;
			}
			fwrite(buffer.getBuffer(), 1, bytes, f);
			fflush(f);
			fclose(f);

			openedFilename.remove(".comp");
		}
	}
}

void ArithCoderTest::decodeFile()
{
	EncodeDialog dialog;
	int result = dialog.exec();
	if (result == QDialog::Accepted)
	{
		FILE* file = fopen(openedFilename.toAscii().constData(), "rb");

		unsigned long long symbolsEncoded = 0;
		unsigned int bits = 0;
		int size = fileSize(file) - sizeof(symbolsEncoded) - sizeof(bits);
		unsigned char* buffer = new unsigned char[size];
		fclose(file);

		FILE* f = fopen(openedFilename.toAscii().constData(), "rb");
		fread(&symbolsEncoded, sizeof(symbolsEncoded), 1, f);
		fread(&bits, sizeof(bits), 1, f);
		fread(buffer, 1, size, f);
		fclose(f);

		BitBuffer bitBuffer(buffer, size, bits, dialog.symbolLength());
		openedFilename.remove(".comp");
		file = fopen(openedFilename.toAscii().constData(), "rb");
		size = fileSize(file);
		char* uncompressed = new char[size + 1];
		uncompressed[size] = 0;
		fread(uncompressed, 1, size, file);
		fclose(file);

		if (dialog.usesCTW())
		{
			clock_t start = clock();
			ContextTree tree(8, 1);
			ArithmeticDecoder decoder(&tree, 1, 8, (symbolsEncoded * dialog.symbolLength() / 8) + 1);
			char* result = decoder.decodeBitStream(bitBuffer, symbolsEncoded);
			clock_t end = clock();

			if (strcmp(uncompressed, (const char*)result) != 0)
			{
				QErrorMessage err(this);
				err.showMessage("Uncompressed file and original are different");
				err.exec();
				delete[] uncompressed;
				return;
			}


			//update table
			char text[20];
			sprintf(text, "%d", end - start);
			dataSource.addEntry(openedFilename, QString("decode withoutCTW"), QString(text), QString(""));
		}
		else
		{
			clock_t start = clock();
			MockProbabilityModel model(uncompressed, size, dialog.symbolLength());
			ArithmeticDecoder decoder(&model, (symbolsEncoded * dialog.symbolLength() / 8) + 1); // tutaj uwazac na przepelnienie
			char* result = decoder.decodeBitStream(bitBuffer, symbolsEncoded);
			clock_t end = clock();

			if (strcmp(uncompressed, (const char*)result) != 0)
			{
				QErrorMessage err(this);
				err.showMessage("Uncompressed file and original are different");
				err.exec();
				delete[] uncompressed;
				return;
			}

			
			//update table
			char text[20];
			sprintf(text, "%d", end - start);
			dataSource.addEntry(openedFilename, QString("decode withoutCTW"), QString(text), QString(""));
		}
		delete[] uncompressed;
	}
}

int ArithCoderTest::fileSize( FILE* file )
{
	fseek (file , 0 , SEEK_END);
	int lSize = ftell (file);
	rewind (file);
	return lSize;
}
